package p48_事件监听器;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 这一节跟上一节类似，都演示了监听器，不过这一节的监听器是基于注解实现的
 * 这一节演示事件发布器如何异步发送事件
 */
@Configuration
@Slf4j
public class _2_基于注解的监听器 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(_2_基于注解的监听器.class);
        context.getBean(MyService.class).doService();
        context.close();
    }

    @Component
    static class MyService {
        //事件发布器
        @Autowired
        private ApplicationEventPublisher publisher;

        public void doService() {
            //使用事件发布器发送事件
            //发送短信事件
            publisher.publishEvent(new _1_基于接口的监听器.SmsEvent("MyService.doService"));
            //发送邮件事件
            publisher.publishEvent(new _1_基于接口的监听器.EmailEvent("MyService.doService"));
            /**
             * 这里由于注入了自定义广播器，所以事件是异步执行的
             */
        }
    }

    @Component
    static class SmsListener {
        /**
         * 使用注解修饰的方法，会变成监听方法，方法的参数就是该监听器感兴趣的事件
         * 下面的监听器方法对发短信事件感兴趣
         */
        @EventListener
        public void listener(_1_基于接口的监听器.SmsEvent event) throws InterruptedException {
            log.info("正在发送短信...");
            log.info("发送短信成功!!!");
        }
    }

    @Component
    static class EmailListener {
        /**
         * 使用注解修饰的方法，会变成监听方法，方法的参数就是该监听器感兴趣的事件
         * 下面的监听器方法对发短信事件感兴趣
         */
        @EventListener
        public void listener(_1_基于接口的监听器.EmailEvent event) throws InterruptedException {
            log.info("正在发送邮件...");
            log.info("发送邮件成功!!!");
        }
    }

    /**
     * 事件发布器使用广播器广播发送事件，而默认的广播器是单线程发布事件
     * 可以给容器中手动添加一个广播器，将来就会使用这个添加的广播器进行事件发布
     * 广播器bean名字必须是applicationEventMulticaster，底层应该是用名字来匹配bean的
     */
    @Bean
    public SimpleApplicationEventMulticaster applicationEventMulticaster(ThreadPoolExecutor pool) {
        //自定义广播器
        SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        //给自定义广播器设置线程池
        multicaster.setTaskExecutor(pool);
        return multicaster;
    }

    //这是一个自定义线程池
    @Bean
    public ThreadPoolExecutor pool() {
        /**
         * jdk的线程池概念复习:
         *  如果核心线程都繁忙，新任务就会被进入阻塞队列
         *  如果阻塞队列满(有界阻塞队列才会满)了，就会启动救急线程来执行任务
         *  如果核心线程+救急线程=最大线程时，还有新任务，就会执行拒绝策略
         *  救急线程空闲时间超过指定值后会被回收
         */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 20, 1, TimeUnit.SECONDS, //核心线程，最大线程，救急线程最大空闲时间
                new ArrayBlockingQueue<>(10, true),     //阻塞队列
                r -> new Thread(r, "线程" + new Random().nextInt(100)),   //线程创建工厂，将来线程池回调这个方法创建线程
                (task, executor) -> task.run());    //拒绝策略，如果线程池线程达到最大线程，就会回调这的方法来处理新任务
        return pool;
    }


}
